﻿using System;
using System.Linq;
using System.Linq.Expressions;
using MathService.MathParser.ExpressionTrees.Nodes;

namespace MathService.MathParser
{
    public class SummOperator : ComplexOperatorItem
    {
        public SummOperator() : this("Σ") { }
        public SummOperator(string Name) : base(Name) { }

        /// <summary>Инициализация оператора</summary>
        /// <param name="Parameters">Блок параметров</param>
        /// <param name="Function">Блок ядра функции</param>
        /// <param name="Parser">Парсер мат.выражения</param>
        /// <param name="Expression">Внешнее мат.выражение</param>
        public override void Initialize(MathExpression Parameters, MathExpression Function, ExpressionParser Parser,
                    MathExpression Expression)
        {
            base.Initialize(Parameters, Function, Parser, Expression);

            var iterator_var = Parameters.Tree
                        .Where(n => n is VariableValueNode && n.Parent is EqualityOperatorNode)
                        .Select(n => ((VariableValueNode)n).Variable)
                        .FirstOrDefault();

            Function.Variable.ClearCollection();
            if(iterator_var != null) Function.Variable.Add(iterator_var);
            Function.Tree
                .Where(n => n is VariableValueNode)
                .Cast<VariableValueNode>()
                .Where(n => !ReferenceEquals(n.Variable, iterator_var))
                .Foreach(n => Function.Variable.Add(n.Variable = Expression.Variable[n.Variable.Name]));

            Parameters.Variable.ClearCollection();
            Parameters.Tree
                .Where(n => n is VariableValueNode)
                .Cast<VariableValueNode>()
                .Where(n => !ReferenceEquals(n.Variable, iterator_var))
                .Foreach(n => Parameters.Variable.Add(n.Variable = Expression.Variable[n.Variable.Name]));
        }

        /// <summary>Метод определения значения</summary>
        /// <returns>Численное значение элемента выражения</returns>
        public override double GetValue(MathExpression ParametersExpression, MathExpression Function)
        {
            var parameters_root = ParametersExpression.Tree.Root;
            var iterator = ((VariableValueNode)parameters_root.Left).Variable;
            var interval = (IntervalNode)parameters_root.Right;
            var min = ((ComputedNode)(interval.Min)).Compute();
            var max = ((ComputedNode)(interval.Max)).Compute();
            var summ = 0.0;
            if(min < max)
                for(int i = (int)min, Max = (int)max; i < Max; i++)
                {
                    iterator.Value = i;
                    summ += Function.Compute();
                }
            else
                for(int i = (int)min, Min = (int)max; i > Min; i--)
                {
                    iterator.Value = i;
                    summ += Function.Compute();
                }
            return summ;
        }

        private delegate double SummDelegate(Delegate d, double Min, double Max, double[] Parameters);
        private static double GetSumm(Delegate d, double Min, double Max, double[] Parameters)
        {
            var pp_len = Parameters.Length;

            var S = 0.0;
            var xx = new object[pp_len + 1];
            Array.Copy(Parameters, 0, xx, 1, pp_len);
            if(Min < Max)
                for(xx[0] = Min; (double)xx[0] < Max; xx[0] = ((double)xx[0]) + 1)
                    S += (double)d.DynamicInvoke(xx);
            else
                for(xx[0] = Min; (double)xx[0] > Max; xx[0] = ((double)xx[0]) - 1)
                    S += (double)d.DynamicInvoke(xx);
            return S;
        }

        /// <summary>Скомпилировать в выражение</summary>
        /// <returns>Скомпилированное выражение System.Linq.Expressions</returns>
        public override Expression Compile(MathExpression ParametersExpression, MathExpression Function)
        {
            var parameters_root = ParametersExpression.Tree.Root;
            var iterator = ((VariableValueNode)parameters_root.Left).Variable;
            var interval_node = parameters_root.Right;
            var min = ((ComputedNode)interval_node.Left).Compile();
            var max = ((ComputedNode)interval_node.Right).Compile();

            var iterator_parameter = Expression.Parameter(typeof(double), iterator.Name);
            var f_parameters = new[] { iterator_parameter };
            var f_body = ((ComputedNode)Function.Tree.Root).Compile(f_parameters);
            var f_expr = Expression.Lambda(f_body, f_parameters).Compile();

            var expr_p = new[]
            {
                Expression.Constant(f_expr), min, max,
                Expression.NewArrayInit(typeof(double), new Expression[0])
            };

            return Expression.Call(new SummDelegate(GetSumm).Method, expr_p);
        }

        /// <summary>Скомпилировать в выражение</summary>
        /// <param name="Function">Ядро функции</param>
        /// <param name="Parameters">Массив параметров</param>
        /// <param name="ParametersExpression">Выражение параметров</param>
        /// <returns>Скомпилированное выражение System.Linq.Expressions</returns>
        public override Expression Compile(MathExpression ParametersExpression, MathExpression Function, ParameterExpression[] Parameters)
        {
            var parameters_root = ParametersExpression.Tree.Root;
            var iterator = ((VariableValueNode)parameters_root.Left).Variable;
            var interval_node = parameters_root.Right;
            var min = ((ComputedNode)interval_node.Left).Compile(Parameters);
            var max = ((ComputedNode)interval_node.Right).Compile(Parameters);

            var iterator_parameter = Expression.Parameter(typeof(double), iterator.Name);
            var f_parameters = Parameters.AppendFirst(iterator_parameter).ToArray();
            var f_body = ((ComputedNode)Function.Tree.Root).Compile(f_parameters);
            var f_expr = Expression.Lambda(f_body, f_parameters).Compile();

            var expr_p = new[]
            {
                Expression.Constant(f_expr), min, max, 
                Expression.NewArrayInit(typeof(double), Parameters)
            };

            return Expression.Call(new SummDelegate(GetSumm).Method, expr_p);
        }
    }
}